package com.zx.idc.backend.gui.api.watch.handler.base;

import com.zx.idc.backend.gui.api.commom.base.BaseIDCCommand;
import com.zx.idc.backend.gui.api.commom.factory.FtpUploadDataFactory;
import com.zx.idc.backend.gui.api.ftp.FtpOperator;
import com.zx.idc.backend.gui.api.ftp.Stat;
import com.zx.idc.backend.gui.api.watch.pojo.FtpUploadResult;
import com.zx.idc.backend.gui.sys.login.MailClient;
import com.zx.idc.common.attributes.FTPReportDataTypeEnum;
import com.zx.idc.common.attributes.NetCommunicationFileTypeEnum;
import com.zx.idc.common.constants.DiskStoragePathConstant;
import com.zx.idc.common.disk.FileStorageFacadeExt4;
import com.zx.idc.common.exception.BeanToXmlIOException;
import com.zx.idc.common.exception.SchemaValidateFailedException;
import com.zx.idc.common.spring.SpringConfigUtil;
import com.zx.idc.common.util.StreamUtils;
import com.zx.idc.ds.base.oper.service.IBaseOperCompanyService;
import com.zx.idc.ds.network.factory.NetCommunicationLogFactory;
import com.zx.idc.ds.network.service.INetCommunicationLogService;
import com.zx.idc.ds.sys.entity.SysUploadFiles;
import com.zx.idc.ds.sys.service.ISysUploadFilesService;
import com.zx.idc.ds.tlog.entity.TLogAlarm;
import com.zx.idc.ds.tlog.factory.TLogAlarmFactory;
import com.zx.idc.ds.tlog.service.ITLogAlarmService;
import com.zx.idc.xml.entity.FileLoad;
import com.zx.idc.xml.utils.JaxbUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.zx.idc.common.attributes.SystemAttributes.FTP_RE_UPLOAD_NUM;
import static com.zx.idc.common.constants.DiskStoragePathConstant.SOURCE_UPLOAD_FILE_DIRECTORY;
import static com.zx.idc.common.constants.FtpUploadResultCodeConstant.*;
import static com.zx.idc.common.lang.StringUtil.append;

public abstract class AbstractFtpUploadResultHandler implements FtpUploadResultHandler {
    protected final Logger log = LoggerFactory.getLogger(getClass());
    private static final Map<String, Integer> fileTypeMap;

    /**
     *  结果代码 和 数据通讯记录的文件类型映射
     */
    static {
        fileTypeMap = new HashMap<>();
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_BASE_DATA),
            getFileType(NetCommunicationFileTypeEnum.BASE_DATA)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_BASE_DATA_MONITORING_ABNORMAL_RECORDS),
            getFileType(NetCommunicationFileTypeEnum.MONITOR_DATA)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_ACCESS_LOG_QUERY_RECORD),
            getFileType(NetCommunicationFileTypeEnum.LOG_QUERY)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_ILLEGAL_INFORMATION_MONITORING_RECORD),
            getFileType(NetCommunicationFileTypeEnum.MONITOR_LOG)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_ILLEGAL_INFORMATION_FILTERING_RECORD),
            getFileType(NetCommunicationFileTypeEnum.FILTER_LOG)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_RESERVED),
            getFileType(NetCommunicationFileTypeEnum.RESERVED)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_ISMS_ACTIVE_STATUS),
            getFileType(NetCommunicationFileTypeEnum.ACTIVE_STATUS)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_ACTIVE_RESOURCE_MONITORING_RECORD),
            getFileType(NetCommunicationFileTypeEnum.ACTIVE_RESOURCE)
        );
        fileTypeMap.put(
            getType(FTPReportDataTypeEnum.FTP_VIOLATION_WEBSITE_MONITORING_RECORD),
            getFileType(NetCommunicationFileTypeEnum.VIOLATION_MONITOR)
        );
    }

    private static String getType(FTPReportDataTypeEnum ftpReportDataTypeEnum) {
        return ftpReportDataTypeEnum.getType();
    }

    private static Integer getFileType(NetCommunicationFileTypeEnum netCommunicationFileTypeEnum) {
        return netCommunicationFileTypeEnum.getFileType();
    }

    @Autowired
    private INetCommunicationLogService iNetCommunicationLogService;

    @Autowired
    private ISysUploadFilesService iSysUploadFilesService;

    @Autowired
    private ITLogAlarmService itLogAlarmService;

    @Autowired
    private FtpOperator ftpOperator;

    @Autowired
    private FileStorageFacadeExt4 fileStorageFacadeExt4;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private IBaseOperCompanyService iBaseOperCompanyService;

    /**
     * 处理模板
     *
     * @param stat
     */
    /**
     * 模板
     *
     * @param stat
     */
    protected final void handleTemplate(Stat stat) {
        log.debug("handle smms result file start!");
        FtpUploadResult ftpUploadResult = formatUploadResult(stat.getName());
        String resultCode = ftpUploadResult.getResultCode();
        log.debug("1.1 get result code is " + resultCode);
        switch (resultCode) {
            case UPLOAD_CODE_SUCCESS:
                //0 成功 修改数据库状态
                log.debug("1.2 upload data is success!");
                log.debug("1.3 upload success process start!");
                uploadSuccessTemplate(stat, ftpUploadResult);
                log.debug("1.3 upload success process done!");
                log.debug("1.4 save communication log and del the reported source file start!");
                //成功后的统一操作 添加到通讯备份 删除上报源文件
                successCommonOperation(ftpUploadResult);
                log.debug("1.4 save communication log and del the reported source file done!");
                break;

            case UPLOAD_CODE_RE_UPLOAD:
                log.debug("1.2 upload data need re-upload!");
                log.debug("1.3 re-upload start!");
                //900 重新上传 删除结果代码文件 不修改数据库状态 重新上传
                reUpload(ftpUploadResult);
                log.debug("1.3 re-upload done!");
                break;

            case UPLOAD_CODE_NEXT_HANDLE:
                //999 处理中 删除结果代码文件 不修改数据库状态
                log.debug("1.2 upload data is Processing!");
                break;

            default:
                log.debug("1.2 upload data is failed!");
                //上报异常 删除结果代码文件 修改数据库状态 添加到告警表 删除上报源文件
                log.debug("1.3 upload failed process start!");
                uploadFailedTemplate(stat, ftpUploadResult);
                log.debug("1.3 upload failed process done!");
                log.debug("1.4 upload failed process start!");
                exceptionCommonOperation(ftpUploadResult);
                log.debug("1.4 upload failed process done!");
                break;
        }
        //统一删除结果代码文件 ftp服务器的
        log.debug("del smms result file start!");
        ftpOperator.rm(stat.getPath());
        log.debug("del smms result file done!");
        log.debug("handle smms result file finish!");
    }

    /**
     * 处理上报成功的模板
     *
     * @param stat
     * @param ftpUploadResult
     */
    private void uploadSuccessTemplate(Stat stat, FtpUploadResult ftpUploadResult) {
        //获取hash之后的文件名
        String filePath = getSourcePath(ftpUploadResult.getFileName(), ftpUploadResult.getTypeCode());
        if (filePath == null) {
            log.debug("fileName:" + ftpUploadResult.getFileName() + " typeCode:" + ftpUploadResult.getTypeCode() + " uploadSuccessTemplate get source path is failed !");
            return;
        }
        uploadSuccess(stat, ftpUploadResult, filePath);
    }

    /**
     * 处理上报异常的模板
     *
     * @param stat
     * @param ftpUploadResult
     */
    private void uploadFailedTemplate(Stat stat, FtpUploadResult ftpUploadResult) {
        //获取hash之后的文件名
        String filePath = getSourcePath(ftpUploadResult.getFileName(), ftpUploadResult.getTypeCode());
        if (filePath == null) {
            log.debug("fileName:" + ftpUploadResult.getFileName() + " typeCode:" + ftpUploadResult.getTypeCode() + " uploadFailedTemplate get source path is failed !");
            return;
        }
        uploadFailed(stat, ftpUploadResult, filePath);
    }

    /**
     * 上报异常的公共操作
     *
     * @param ftpUploadResult
     */
    private void exceptionCommonOperation(FtpUploadResult ftpUploadResult) {
        String fileName = ftpUploadResult.getFileName();
        String typeCode = ftpUploadResult.getTypeCode();
        String sourcePath = getSourcePath(fileName, typeCode);
        if (sourcePath == null) {
            log.debug("fileName:" + fileName + " typeCode:" + typeCode + " exceptionCommonOperation get source path is failed !");
            return;
        }
        //添加到告警表

        try (FileInputStream fileInputStream = new FileInputStream(sourcePath);) {
            TLogAlarm tLogAlarm = TLogAlarmFactory.getReportFailed(
                String.format("上报异常,上报数据类型代码：%s, 错误结果代码:%s", ftpUploadResult.getTypeCode(), ftpUploadResult.getResultCode())
            );
            itLogAlarmService.insertTLogAlarm(tLogAlarm, fileInputStream, ftpUploadResult.getFileName());
            mailClient.sendAlarmEmail("有新的告警日志生成，来自上报文件异常", "具体请查看ID为" + tLogAlarm.getId() + "的告警日志。");
        } catch (Exception e) {
            String msg = "上报文件异常 添加告警日志保留源文件失败";
            log.error(msg, e);
            TLogAlarm tLogAlarm = TLogAlarmFactory.getReportFailed(msg);
            insertTLogAlarm(tLogAlarm);
            mailClient.sendAlarmEmail("有新的告警日志生成，来自上报文件异常", "具体请查看ID为" + tLogAlarm.getId() + "的告警日志。");
            return;
        }
        deleteFile(sourcePath);
        deleteHashPathByFileName(fileName, typeCode);
    }

    /**
     * 重新上报
     *
     * @param ftpUploadResult
     */
    private void reUpload(FtpUploadResult ftpUploadResult) {
        String oldfileName = ftpUploadResult.getFileName();
        String typeCode = ftpUploadResult.getTypeCode();
        String sourceFilePath = getSourcePath(oldfileName, typeCode);
        if (sourceFilePath == null) {
            log.debug("fileName:" + oldfileName + " typeCode:" + typeCode + " reUpload get source path is failed !");
            return;
        }
        //path:{idcHome/type/生成当前文件时间.xml}
        String uploadPath = BaseIDCCommand.getUploadPath(typeCode);
        String newFileName = uploadPath.split("/")[4];
        //读出源文件
        try {
            //保存源数据

            try (FileInputStream fileInputStream = new FileInputStream(sourceFilePath);) {
                String rootPath = SpringConfigUtil.get(DiskStoragePathConstant.SOURCE_UPLOAD_FILE_DIRECTORY);
                String path = fileStorageFacadeExt4.saveFile(fileInputStream, newFileName, rootPath);
                //根据文件名映射文件路径
                iSysUploadFilesService.insert(
                    new SysUploadFiles()
                        .setFileName(newFileName)
                        .setUploadFilePath(path)
                        .setTypeCode(typeCode)
                        .setCreateTime(LocalDateTime.now())
                );
            } catch (Exception e) {
                String msg = "重新上报保存源数据失败";
                log.error(msg, e);
                throw e;
            }
            List<String> strings = Files.readAllLines(Paths.get(sourceFilePath), StandardCharsets.UTF_8);
            String sourceData = append(strings, "\n");
            //加密流程
            String idcId = iBaseOperCompanyService.getIdcId();
            FileLoad fileLoad = FtpUploadDataFactory.getFtpUploadFileLoadEntity(sourceData, idcId);

            if (fileLoad == null) {
                Integer alarmId = insertReportFailTLogAlarm(sourceData, uploadPath, "获取加密上报数据实体失败");
                mailClient.sendAlarmEmail("有新的告警日志生成，来自加密上报数据", "具体请查看ID为" + alarmId + "的告警日志。");
                return;
            }
            InputStream inputStream = JaxbUtils.beanToXmlFile(fileLoad);
            boolean uploadResult = uploadFile(
                inputStream,
                uploadPath,
                FTP_RE_UPLOAD_NUM
            );
            if (!uploadResult) {
                Integer alarmId = insertReportFailTLogAlarm(sourceData, uploadPath, "上报上传失败");
                mailClient.sendAlarmEmail("有新的告警日志生成，来自文件上传", "具体请查看ID为" + alarmId + "的告警日志。");
            }
        } catch (Exception e) {
            String msg = "读出源文件失败,添加到告警表中,请手动重新上报";
            log.error(msg, e);
            TLogAlarm tLogAlarm = TLogAlarmFactory.getReportFailed(msg);
            insertTLogAlarm(tLogAlarm, e.getMessage());
            mailClient.sendAlarmEmail("有新的告警日志生成，来自读出源文件", "具体请查看ID为" + tLogAlarm.getId() + "的告警日志。");
        }
    }

    /**
     * 添加 通讯记录
     *
     * @param ftpUploadResult
     */
    private void successCommonOperation(FtpUploadResult ftpUploadResult) {
        String fileName = ftpUploadResult.getFileName();
        String typeCode = ftpUploadResult.getTypeCode();
        //添加.xml后缀,判断如果没有.xml后缀就添加
        if (!fileName.contains(".xml")) {
            fileName = fileName + ".xml";
        }
        //根据文件名获取保留文件的hash路径
        String sourceFilePath = getSourcePath(fileName, typeCode);
        if (sourceFilePath == null) {
            log.debug("fileName:" + fileName + " typeCode:" + typeCode + " successCommonOperation get source path is failed !");
            return;
        }
        FileInputStream fileInputStream = null;
        try {
            log.debug("save net communication log start!");
            fileInputStream = new FileInputStream(sourceFilePath);
            iNetCommunicationLogService.insertNetCommunicationLog(
                NetCommunicationLogFactory
                    .getBasicUploadEntity()
                    .setFileName(fileName)
                    .setFileType(getFileTypeByTypeCode(typeCode)),
                fileInputStream
            );
            log.debug("save net communication log done!");
        } catch (Exception e) {
            log.error("上报记录备份失败", e);
        } finally {
            StreamUtils.closeStream(fileInputStream);
        }

        //删除源数据
        deleteFile(sourceFilePath);
        //删除数据库记录
        deleteHashPathByFileName(fileName, typeCode);
    }

    /**
     * 根据文件名删除hash文件的路径
     *
     * @param fileName 文件名
     */
    private void deleteHashPathByFileName(String fileName, String typeCode) {
        log.debug("del upload file start!");
        iSysUploadFilesService.delete(
            new SysUploadFiles()
                .setFileName(fileName)
                .setTypeCode(typeCode)
                .setCreateTime(LocalDateTime.now())
        );
        log.debug("del upload file done!");
    }


    /**
     * 获取上报源文件的路径
     *
     * @param fileName 文件名
     * @param typeCode 类型代码
     * @return 上报源文件的路径
     */
    protected String getSourcePath(String fileName, String typeCode) {
        //添加.xml后缀,判断如果没有.xml后缀就添加
        if (!fileName.contains(".xml")) {
            fileName = fileName + ".xml";
        }
        SysUploadFiles getUploadFilePath = iSysUploadFilesService.getUploadFilePath(fileName, typeCode);
        if (getUploadFilePath == null || StringUtils.isEmpty(getUploadFilePath.getUploadFilePath())) {
            TLogAlarm tLogAlarm = TLogAlarmFactory.getReportFailed("上报源文件不存在,请手动重新上报");
            insertTLogAlarm(tLogAlarm);
            mailClient.sendAlarmEmail("有新的告警日志生成，来自上报源文件", "具体请查看ID为" + tLogAlarm.getId() + "的告警日志。");
            return null;
        }
        String rootPath = SpringConfigUtil.get(SOURCE_UPLOAD_FILE_DIRECTORY);

        //添加到通讯记录
        return rootPath + "/" + getUploadFilePath.getUploadFilePath();
    }

    /**
     * 本地删除文件
     *
     * @param filePath 文件全路径
     */
    private void deleteFile(String filePath) {
        //删除源数据
        try {
            Files.deleteIfExists(Paths.get(filePath));
        } catch (IOException e) {
            log.error("删除上报源文件失败", e);
        }
    }

    /**
     * 文件名转换成 上报结果对象
     *
     * @param name
     * @return
     */
    protected FtpUploadResult formatUploadResult(String name) {
        String[] split = name.split("-");
        return new FtpUploadResult()
            .setTypeCode(split[0])
            .setFileName(split[1])
            .setResultCode(split[2]);
    }

    /**
     * 根据结果代码类型获取文件类型 添加数据通讯记录使用
     *
     * @return
     */
    private Integer getFileTypeByTypeCode(String resultCode) {
        return fileTypeMap.get(resultCode);
    }

    /**
     * 上传文件
     * 系统性的上传失败
     * 网络错误重试
     *
     * @param is   文件流
     * @param path 上传到远端的路径
     * @param num  失败后的重试次数
     * @return 上传是否成功
     */
    private boolean uploadFile(InputStream is, String path, int num) {
        try {
            if (ftpOperator.put(is, path)) {
                return true;
            }
            if (num <= 0) {
                return false;
            }
            return uploadFile(is, path, num - 1);
        } catch (Throwable e) {
            if (SocketException.class.equals(e.getClass())) {
                return uploadFile(is, path, num - 1);
            } else if (ConnectException.class.equals(e.getClass())) {
                return uploadFile(is, path, num - 1);
            }
            throw e;
        } finally {
            StreamUtils.closeStream(is);
        }
    }


    /**
     * 添加上报失败告警记录
     *
     * @param srcData   源数据字符串
     * @param uploadDir 上传目录
     * @param comments  备注
     */
    private Integer insertReportFailTLogAlarm(String srcData, String uploadDir, String comments) {
        TLogAlarm tLogAlarm = null;
        try {
            tLogAlarm = TLogAlarmFactory.getReportFailed(comments);
            itLogAlarmService.insertTLogAlarm(tLogAlarm, IOUtils.toInputStream(srcData), uploadDir);
            return tLogAlarm.getId();
        } catch (SchemaValidateFailedException | BeanToXmlIOException e) {
            log.error("添加上报失败告警记录异常", e);
            tLogAlarm = TLogAlarmFactory.getReportFailed(comments);
            insertTLogAlarm(tLogAlarm, "保存源文件失败");
            mailClient.sendAlarmEmail("有新的告警日志生成，来自添加告警记录", "具体请查看ID为" + tLogAlarm.getId() + "的告警日志。");
        }
        return tLogAlarm.getId();
    }

    /**
     * 添加告警记录
     *
     * @param tLogAlarm 实体
     * @param comments  备注
     */
    private void insertTLogAlarm(TLogAlarm tLogAlarm, String comments) {
        itLogAlarmService.insert(tLogAlarm.setComments(append(tLogAlarm.getComments(), ":", comments)));
    }

    /**
     * 添加告警记录
     *
     * @param tLogAlarm 实体
     */
    private void insertTLogAlarm(TLogAlarm tLogAlarm) {
        insertTLogAlarm(tLogAlarm, "");
    }

}
